/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package web.compliance.mail.browser;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.DateTools.Resolution;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import java.text.ParseException;
import java.util.Date;
import org.apache.log4j.Logger;
import org.apache.lucene.document.TextField;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

/**
 *
 * @author Aya
 */
public class SearchEngine {
    
    private IndexWriter writer;
    private Directory index;
    static Logger log = Logger.getLogger(SearchEngine.class.getName());
    
    /**
     * Constructor of the Search Engine..
     * Initializes the analyzers and the index directory as well as the configurations
     */
    public SearchEngine(){
        StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
        index = new RAMDirectory();
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_43, analyzer);
        try {
            writer = new IndexWriter(index, config);
        } catch(Exception e){
            log.warn("Error creating the writer");
        }
    }
    
    /**
     * Adds an email to the index of the search engine
     * @param mail The mail object to be indexed
     * @throws Exception When adding the document raises an exception
     */
    public void addMail(Mail mail) throws IOException{
        Document doc = new Document();
        doc.add(new TextField("content", mail.getContent(), Field.Store.YES));
        doc.add(new TextField("subject", mail.getSubject(), Field.Store.YES));
        doc.add(new TextField("sender", mail.getFrom(), Field.Store.YES));
        String date = DateTools.dateToString(mail.getDate(), Resolution.DAY);
        doc.add(new TextField("date", date, Field.Store.YES));
        writer.addDocument(doc);
        writer.commit();
    }
    
    /**
     * Performs the basic function of filtering the mails by a certain field
     * @param fieldname The name of the field to consider
     * @param value The keyword to filter with
     * @param size The maximum number of results to return
     * @return The list of mails which satisfy the filter
     */
    public List<Mail> getMails(String fieldname, String value, int size) {
        List<Mail> mails = new ArrayList<Mail>();
        try {
            StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
            IndexReader reader = DirectoryReader.open(index);
            IndexSearcher searcher = new IndexSearcher(reader);
            
            Query query = new QueryParser(Version.LUCENE_43, fieldname, analyzer).parse(value);
            
            ScoreDoc[] hits = searcher.search(query, size).scoreDocs;
            for (int i = 0; i < hits.length; i++){
                mails.add(getMailInDoc(searcher.doc(hits[i].doc)));
            }
        }
        catch (ParseException ex) {
            log.warn("Java Parse Error");
        } catch (IOException ex) {
            log.warn("IOExcpetion Error");
        } catch (org.apache.lucene.queryparser.classic.ParseException ex) {
            log.warn("Lucene Parse Error");
        }
        return mails;
    }
    
    /**
     * Applies a date filter to a searched query
     * @param fieldname The field to be filtered
     * @param value The keyword to be searched
     * @param from The start of the date range (can be null)
     * @param to The end of the date range (can be null)
     * @param size The size of the hits for the query
     * @return 
     */
    public List<Mail> getMailsWithFilter(String fieldname, String value, Date from, Date to, int size) {
        List<Mail> mails = new ArrayList<Mail>();
        try {
            StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
            IndexReader reader = DirectoryReader.open(index);
            IndexSearcher searcher = new IndexSearcher(reader);
            String upper = null;
            String lower = null;
            if(from != null)
                lower = DateTools.dateToString(from, Resolution.DAY);
            if(to != null)
                upper = DateTools.dateToString(to, Resolution.DAY);
            
            Query query = new QueryParser(Version.LUCENE_43, fieldname, analyzer).parse(value);
            TermRangeQuery rangeQuery = TermRangeQuery.newStringRange("date", lower, upper, lower!=null, upper!=null);
            Filter dateFilter = new QueryWrapperFilter(rangeQuery); 
            
            ScoreDoc[] hits = searcher.search(query, dateFilter, size).scoreDocs;
            for (int i = 0; i < hits.length; i++){
                mails.add(getMailInDoc(searcher.doc(hits[i].doc)));
            }
        }
        catch (ParseException ex) {
            log.warn("Java Parse Error");
        } catch (IOException ex) {
            log.warn("IOExcpetion Error");
        } catch (org.apache.lucene.queryparser.classic.ParseException ex) {
            log.warn("Lucene Parse Error");
        }
        return mails;
    }
    
    /**
     * Applies a date filter on the whole list of mails
     * @param from The start of the date range
     * @param to The end of the date range
     * @param size The size of the hits for the query
     * @return 
     */
    public List<Mail> getMailsInRange(Date from, Date to, int size){
        // Parse the dates to strings
        String upper = null;
        String lower = null;
        if(from != null)
            lower = DateTools.dateToString(from, Resolution.DAY);
        if(to != null)
            upper = DateTools.dateToString(to, Resolution.DAY);
        // Search for mails within the given range
        List<Mail> mails = new ArrayList<Mail>();
        try {
            //StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
            IndexReader reader = DirectoryReader.open(index);
            IndexSearcher searcher = new IndexSearcher(reader);
            
            TermRangeQuery query = TermRangeQuery.newStringRange("date", lower, upper, lower!=null, upper!=null);
            //Query query = new QueryParser(Version.LUCENE_43, fieldname, analyzer).parse(value);
            
            ScoreDoc[] hits = searcher.search(query, size).scoreDocs;
            for (int i = 0; i < hits.length; i++){
                mails.add(getMailInDoc(searcher.doc(hits[i].doc)));
            }
        }
        catch (ParseException ex) {
            log.warn("Java Parse Error");
        } catch (IOException ex) {
            log.warn("IOExcpetion Error");
        } 
        //catch (org.apache.lucene.queryparser.classic.ParseException ex) {
        //    log.warn("Lucene Parse Error");
        //}
        return mails;
    }
    
    /**
     * A helper method that extracts a mail object from a document
     * @param doc The Lucene document to be converted to mail
     * @return The mail object
     * @throws ParseException 
     */
    private Mail getMailInDoc(Document doc) throws ParseException{
        Mail m = new Mail();
        m.setContent(doc.get("content"));
        m.setDate(DateTools.stringToDate(doc.get("date")));
        m.setFrom(doc.get("sender"));
        m.setSubject(doc.get("subject"));
        return m;
    }
    
    /**
     * Clears all the documents in the index
     */
    public void clearIndex() throws IOException{
        writer.deleteAll();
        writer.commit();
    }
}
